import java.io.*;
import java.util.*;
import common.*;

public class Process extends Thread
{
	private MessagePasser postman;
	private Interface shell;
	private ArrayList<Resource> local_files;
	
	// for ME_TOKENLESS
	private CriticalSectionRecord cs_in_use = null;		// records of the CS that this process is granted to use
														// // * note: this can be a multiple.
														//     It is possible for a process to receive
														//     multiple permissions (= multiple CS) at a time,
														//     but, it still only be in a CS at a moment.

	// * note: these can be a multiple.
	private ArrayList<CriticalSectionRecord> cs_my_requests_granted;	// records of the CS that this process is granted to use
	protected ArrayList<CriticalSectionRecord> cs_my_requests;			// records of the current requests of this process.
	private ArrayList<CriticalSectionRecord> cs_others_requests;	// records of requests of other process.
	
	// for ME_TOKEN
	
	
	// process state var
	private boolean running = true;
	protected int myID, sleepTime;
	private Random rand;
	private boolean cs_run_msg_flag = false;
	
	// ----------------------------
	// A quick note for the ME implementation:
	//   - The variables for ME are declared 'a few lines above' and instantiated in the 'constructor' @ Process.java
	//   - Interface.java makes records for own requests, right after the msg is sent to everyone 'run()' @ Interface.java
	//   - The message handlers for ME are located in  'public void receive( Message msg )' @ Process.java
	//   - The things to do when RELEASE are in  'public void run()' @ Process.java
	
	public Process( int myID, int sleepTime ){
		this.myID = myID;
		this.sleepTime = sleepTime;
		rand = new Random();
		
		// establishes the network
		try
		{
			postman = new MessagePasser(myID, this);	////new MessagePasser(this, myID);
		}
		catch(IOException e)
		{
			System.out.println("Could not build MessagePasser, quitting");
			System.exit(0);
		}
		
		// resources
		local_files = new ArrayList<Resource>();
		// all proc has 'Conf.N_RESOURCES_PER_NODE' of resources
		for( int i = 0; i < Conf.N_RESOURCES_PER_NODE; i++ ){
			local_files.add( new Resource( "file_" + myID + "_" + i + ".txt", "/", myID ) );
		}
		
		this.print_filelist();
		
		// interface (console)
		shell = new Interface( this );		// if the preemptive mgmt_console (Monitor.java) is on, don't create the Interface
		
		
		// ME related
		if( Conf.ME_SCHEME == Def.ME_TOKENLESS ){	// for TOKENLESS (Ricart-Agrawala)
			cs_my_requests = new ArrayList<CriticalSectionRecord>();
			cs_my_requests_granted = new ArrayList<CriticalSectionRecord>();
			cs_others_requests = new ArrayList<CriticalSectionRecord>();
		
		}else{	// for TOKEN-based (Raymond)
			
			// create data structures for the Token-based ME scheme
			
		}
	}//end-of-constructor
	
	
	
	//
	// Thread core
	//
	public void run()
	{
		// autoexec
		if( !Conf.MGMT_CONSOLE ){
			//shell.turnon();	// prevent the screen from getting swamped by prompts
			//shell.start();
		}
		
		// Execute the main loop of the program
		while( running ){	// running -- for process simulation. !running -- for net test
			
			// running critical section - doing 1 cs per loop
			if( Conf.ME_SCHEME == Def.ME_TOKENLESS ){	// for TOKENLESS (Ricart-Agrawala)
				if( cs_in_use != null ){
					// check the time elapsed in the CS
					if( cs_in_use.duration_ms <= System.currentTimeMillis() - cs_in_use.grant_time_ms ){
						Utils.print_log( "-proc(" + myID+ "): done with the CS of \"" + cs_in_use.get_location_n_name() + "\"" );
						// I'm done with the CS, so let's RELEASE it
						if( cs_in_use.get_operation_type() == Def.ME_OPR_WRITE )
							send( Def.CSFREE, cs_in_use.owner, cs_in_use.get_location_n_name(), new Integer( ( cs_in_use.crc + 1 ) % 64 )  );
						cs_in_use = null;
						
						// send GRANT for the requests in 'cs_others_requests'
						for( int i = 0; i < cs_others_requests.size(); i++ ){
// **** TODO: check AGAIN the resource is not in my interest
							CriticalSectionRecord temp = cs_others_requests.remove( i );
							send( Def.GRANT, temp.get_requester(), temp.get_location_n_name(), null );
							//send( Def.GRANT, myID, temp.get_location_n_name(), null );
						}
					}else{
						if( cs_run_msg_flag ){
							Utils.print_log( "-proc(" + myID+ "): running CS of \"" + cs_in_use.get_location_n_name() + "\"" );
							cs_run_msg_flag = false;	// make it shut up
						}
					}
				}
				// running the next CS
				if( cs_in_use == null && cs_my_requests_granted.size() > 0 ){	// if i'm not using any CS, but have granted
					cs_in_use = cs_my_requests_granted.remove( 0 );
					cs_run_msg_flag = true;
					cs_in_use.grant_time_ms = System.currentTimeMillis();	// check-in with the current UTC
				}
				// timeout for long-waiting 'this.cs_my_requests' 
				for( int i = 0; i < cs_my_requests.size(); i++ ){
					if( System.currentTimeMillis() - cs_my_requests.get( i ).get_request_time() > Conf.ME_REQUEST_TIMEOUT_MS ){
						cs_my_requests.remove( i );
					}
				}
			
				
			}else{	// for TOKEN-based (Raymond)
				
				// Running CS + RELEASE actions + switch the next CS granted
				
			}//end-of-critical_section_running
			
			
			// sleep -- clock imitation
			try{
				sleep( sleepTime );
			}catch( InterruptedException e ){
				Utils.print_log( "ERROR: critical error (check your code @voa931) -- Process.java" );
			}
		}//end-of-while
	//	System.out.println( "Process: pid=" + myID + " is terminated" );
		
		//// ------------------------- physical network testing routine ------------------------- ////
		int partnerID = 0;
		for(int i = 0; running; i++)
		{
			try
			{
				Thread.sleep(sleepTime);
				partnerID = rand.nextInt(MessagePasser.getNetworkSize());
				while(partnerID == myID)
				{
					partnerID = rand.nextInt(MessagePasser.getNetworkSize());
				}
				////postman.send(partnerID, Integer.toString(i)+"\n");
				Message msg = new Message( Def.PING, partnerID, myID, System.currentTimeMillis(), "", null );
				postman.send( msg );
			}
			catch(Exception e)
			{
				System.out.println("Error sending message: ");
				e.printStackTrace();
			}
		}
		System.out.println("Node: " + myID + " died");
		//// ------------------------- physical network testing routine ------------------------- ////

	}//end-of-run()
	
	
	public void receive(String msg)
	{	
		if (msg.equals("close"))
		{
			System.out.println("Partner closed connection.");
			shutdown();
		}
		System.out.println(myID + " Received: " + msg);
	}
	
	/**
	 *  this method invoked by postman.receive(), when postman receives a message and it needs to be handled at the process level.
	 */
	public void receive( Message msg ){
		
		Utils.print_log( "-proc(" + myID + "): is handling a msg[t=" + msg.get_type() + "] from proc(" + msg.get_sender_id() + ")" );
		
		// msg, ls
		if( msg.get_type() == Def.MSG ){	// simple msg from a node -> all i need to do is print it out thru the interface
			shell.show_message( msg );
			return;
		}else if( msg.get_type() == Def.LS ){
			ArrayList<String> temp = this.get_filelist();
			String ls = "";
			for( int i = 0; i < temp.size(); i++ ){
				ls = ls + temp.get( i ) + "\n";
			}
			send( Def.ACK, msg.get_sender_id(), ls, null );
			return;
		}else if( msg.get_type() == Def.ACK ){
			// ls return
			if( msg.get_content() != null )
				System.out.print( msg.get_content() );
			return;
		}
		
		if( Conf.ME_SCHEME == Def.ME_TOKENLESS ){		// for TOKENLESS (Ricart-Agrawala)
			// msg handler for the tokenless ME scheme
			switch( msg.get_type() ){
				case Def.REQUEST_READ:
					if( this.is_waiting_for( msg.get_content() ) ){
						// if this process is waiting for the requested CS, compare the timestamps
						if( msg.get_timestamp() <= get_my_request_time( msg.get_content() ) ){
							// the incoming request was made earlier: I gotta yield + reply GRANT
							send( Def.GRANT, msg.get_sender_id(), msg.get_content(), null );	// if the requested files is local (mine), resource record will be updated in send()
							
						}else{
							// my request was made earlier: I don't reply + enqueue the request
							cs_others_requests.add( new CriticalSectionRecord( msg ) );	//enqueue
						}
						
					}else if( cs_in_use == null || !( cs_in_use.get_location_n_name() ).equals( msg.get_content() ) ){
						// if this process doesn't use the requested CS, reply GRANT
						send( Def.GRANT, msg.get_sender_id(), msg.get_content(), null );	// if the requested files is local (mine), resource record will be updated in send()
						
					}else if( ( cs_in_use.get_location_n_name() ).equals( msg.get_content() ) && cs_in_use.get_operation_type() == Def.ME_OPR_READONLY ){
						// if this process use the requested CS and its operation is READONLY, reply GRANT
						send( Def.GRANT, msg.get_sender_id(), msg.get_content(), null );	// if the requested files is local (mine), resource record will be updated in send()
						
					}else if( ( cs_in_use.get_location_n_name() ).equals( msg.get_content() ) && cs_in_use.get_operation_type() == Def.ME_OPR_WRITE ){
						// if this process use the requested CS and its operation is WRITE, don't reply + enqueue the request
						cs_others_requests.add( new CriticalSectionRecord( msg ) );	//enqueue
					}
					break;
					
				case Def.REQUEST_WRITE:
					if( this.is_waiting_for( msg.get_content() ) ){
						// if this process is waiting for the requested CS, compare the timestamps
						if( msg.get_timestamp() <= get_my_request_time( msg.get_content() ) ){
							// the incoming request was made earlier: I gotta yield + reply GRANT
							send( Def.GRANT, msg.get_sender_id(), msg.get_content(), null );	// if the requested files is local (mine), resource record will be updated in send()
							
						}else{
							// my request was made earlier: I don't reply + enqueue the request
							cs_others_requests.add( new CriticalSectionRecord( msg ) );	//enqueue
						}
						
					}else if( cs_in_use == null || !( cs_in_use.get_location_n_name() ).equals( msg.get_content() ) ){
						// if this process doesn't use the requested CS, reply GRANT
						send( Def.GRANT, msg.get_sender_id(), msg.get_content(), null );	// if the requested files is local (mine), resource record will be updated in send()
						
					}else if( ( cs_in_use.get_location_n_name() ).equals( msg.get_content() ) ){
						// if this process use the requested CS (whatever the operation is), don't reply + enqueue the request
						cs_others_requests.add( new CriticalSectionRecord( msg ) );	//enqueue
					}
					break;
					
				case Def.GRANT:
					for( int i = 0; i < cs_my_requests.size(); i++ ){
						if( msg.get_content().equals( cs_my_requests.get( i ).get_location_n_name() ) ){
							// if the GRANT msg came with the resource header --> the sender is the file owner.
							if( msg.get_attachment() != null && msg.get_attachment() instanceof Resource ){
								if( cs_my_requests.get( i ).get_operation_type() == Def.ME_OPR_READONLY ){
									cs_my_requests.get( i ).duration_ms = ( ( Resource )msg.get_attachment() ).get_readtime_ms();
									cs_my_requests.get( i ).crc = ( ( Resource )msg.get_attachment() ).get_crc();
								}else if( cs_my_requests.get( i ).get_operation_type() == Def.ME_OPR_WRITE ){
									cs_my_requests.get( i ).duration_ms = ( ( Resource )msg.get_attachment() ).get_writetime_ms();
									cs_my_requests.get( i ).crc = ( ( Resource )msg.get_attachment() ).get_crc();
								}
							}
							
							// mark the sender's field as the GRANT, on my_request list
							if( cs_my_requests.get( i ).granted_by.indexOf( new Integer( msg.get_sender_id() ) ) == -1 )
								Utils.print_log( "cs_grant_check = ok" );
							cs_my_requests.get( i ).granted_by.add( msg.get_sender_id() );
							Utils.print_log( "-proc(" + myID + "): " + "received a GRANT for \"" + msg.get_content() + "\" from proc(" + msg.get_sender_id() + ")" );
							
							// if the request got GRANTed by everyone --> I can run the CS: move the CS to 'cs_my_requests_granted' 
							if( postman.is_this_everyone( cs_my_requests.get( i ).granted_by ) ){
								
								Utils.print_log( "***" + cs_my_requests.size() );
								
								CriticalSectionRecord temp = cs_my_requests.remove( i );
								cs_my_requests_granted.add( temp );
								
								Utils.print_log( "-proc(" + myID + "): " + "got all GRANTs for \"" + msg.get_content() + "\"" );
								
								break;	// break for( int i = 0; i < cs_my_requests.size(); i++ )
							}
						}
					}//end-of-for
					break;

				case Def.CSFREE:
					for( int i = 0; i < local_files.size(); i++ ){
						if( msg.get_content().equals( local_files.get( i ).get_location_n_name() ) ){
							local_files.get( i ).set_granted_for( Def.NOT_ASSIGNED );
							if( msg.get_attachment() != null && msg.get_attachment() instanceof Integer ){
								Integer new_crc = ( Integer )msg.get_attachment();
								//local_files.get( i ).set_crc(  );								
							}
							
							break;	// break 'for'
						}
					}
					break;
					
				default:
					Utils.print_log( "ERROR: " + msg.get_type() + " is unknown message type (check your code @qfv032) -- Process.java" );
			}//end-of-switch
		}else if( Conf.ME_SCHEME == Def.ME_TOKEN ){		// for TOKEN-based (Raymond)
			// msg handler for the token-based ME scheme
			switch( msg.get_type() ){
				case Def.REQUEST_READ:
					break;
				case Def.REQUEST_WRITE:
					break;
				case Def.GRANT:	//(?)
					break;
				case Def.TOKEN:
					break;
				default:
					Utils.print_log( "ERROR: " + msg.get_type() + " is unknown message type (check your code @vhq535) -- Process.java" );
			}//end-of-switch
		}//end-of-if~elseif
	}//end-of-receive( Message msg )
	
	public void send( int msg_type, int rcv_id, String str, Object attach ){
		if( Conf.ME_SCHEME == Def.ME_TOKENLESS ){
			// if the msg_type is GRANT and the original request is for one of my resource,
			// I'll reply GRANT with the resource information
			if( msg_type == Def.GRANT && attach == null ){
				for( int i = 0; i < local_files.size(); i++ ){
					// see if the request is for one of my local files
					if( str.equals( local_files.get( i ).get_location_n_name() ) ){	// if it's my local file
						// attach the file header to the outgoing msg
						attach = local_files.get( i );
						if( local_files.get( i ).get_granted_for() == Def.ME_OPR_WRITE ){
							Utils.print_log( "ERROR: you gotta think about here (check your code @dpf394) -- Process.java" );
						}
						local_files.get( i ).set_granted_for( msg_type );	// WRITE OR READ
						
						break;
					}
				}
			}
			
			// create + send a message
			Message msg = new Message( msg_type, rcv_id, myID, System.currentTimeMillis(), str, attach );
			postman.send( msg );
			
		}else{ 	// for TOKEN-based (Raymond)
			Message msg = new Message( msg_type, rcv_id, myID, System.currentTimeMillis(), str, attach );
				// Message( int _type, int _rcvID, int _sdrID, long _timestamp, String _content, Object _attach )
			postman.send( msg );
		}
	}
	
	public void addPartner(int partnerID)
	{
		if(postman.addConnection(partnerID))
		{
			System.out.println("Successfully connected to partner " + partnerID);
		}
		else
		{
			System.out.println("Could not connect to partner " + partnerID);
		}
	}
	
	public void shutdown()
	{
		shell.turnoff();
		
		try
		{
			postman.close();
		}
		catch (Exception e)
		{
			System.out.println("Problem closing the MessagePasser");
		}
		running = false;
	}
	
	public MessagePasser get_postman(){ return postman; }
	public Interface get_shell(){ return shell; }
	public ArrayList<Resource> get_files(){ return local_files; }
	
	// for ME TOKENLESS
	public boolean is_waiting_for( String location_n_name ){
		for( int i = 0; i < cs_my_requests.size(); i++ ){
			if( location_n_name.equals( cs_my_requests.get( i ).get_location_n_name() ) )
				return true;
		}
		return false;
	}
	
	public long get_my_request_time( String location_n_name ){
		for( int i = 0; i < cs_my_requests.size(); i++ ){
			if( location_n_name.equals( cs_my_requests.get( i ).get_location_n_name() ) )
				return cs_my_requests.get( i ).get_request_time();
		}
		
		Utils.print_log( "ERROR: critical error (check your code @dfo348) -- Process.java" );
		return Def.ME_NULL;	// shouldn't be happening		
	}
	
	public ArrayList<String> get_filelist(){
		ArrayList<String> list = new ArrayList<String>();
		
		for( int i = 0; i < local_files.size(); i++ )
			list.add( local_files.get( i ).get_location_n_name() );
		
		return list;
	}
	
	public void print_filelist(){
		ArrayList<String> list = get_filelist();
		for( int i = 0; i < list.size(); i++ )
			Utils.print_log( list.get(i) );
	}
}
